<?php
/**
 * Created by PhpStorm.
 * User: shooke
 * Date: 17-2-15
 * Time: 上午10:29
 */

namespace corephp\view;


class CoreView
{
    /**
     * 默认配置
     *
     * @var unknown
     */
    public $config = [
        'TPL_TEMPLATE_PATH'   => './template/', // 模板目录，一般不需要修改
        'TPL_TEMPLATE_SUFFIX' => '.html', // 模板后缀，一般不需要修改
        // 模板左右标记
        'TPL_TEMPLATE_LEFT'   => '{',
        'TPL_TEMPLATE_RIGHT'  => '}',
        // 编译文件缓存
        'TPL_COMPILE_PATH'    => './runtime/tpl-compile/', // 模板缓存目录，一般不需要修改
        'TPL_COMPILE_SUFFIX'  => '.php', // 模板缓存后缀,一般不需要修改
        // 'TPL_CACHE_TYPE'=>'file',//数据缓存类型，后期添加，为空或Memcache或SaeMemcache，其中为空为普通文件缓存，
        'TPL_LAYOUT_FILE'     => 'layout', // 布局文件路径
    ];

    /**
     * 存放变量信息
     *
     * @var unknown
     */
    protected $vars = [];

    /**
     * 替换规则
     *
     * @var unknown
     */
    protected $_replace = [
        /**
         * 字符串替换
         */
        'str' => [
            'search'  => [],
            'replace' => []
        ],
        /**
         * 正则替换
         */
        'reg' => [
            'search'  => [],
            'replace' => []
        ]
    ];

    /**
     * 模板
     * @var string
     */
    private $template = '';


    public function __construct($config = [])
    {
        $this->setConfig($config); // 合并新配置
        define('TPL_INC', true);
    }


    /**
     * 模板赋值
     * @param $name
     * @param string $value
     */
    public function assign($name, $value = '')
    {
        if (is_array($name) || is_object($name)) {
            foreach ($name as $k => $v) {
                $this->vars[$k] = $v;
            }
        } else {
            $name = (string)$name;
            $this->vars[$name] = $value;
        }
    }

    /**
     * 进行视图处理，并返回结果
     * @param string $tpl
     * @param bool $layout
     * @return mixed
     * @throws Exception
     */
    public function fetch($tpl = '',$layout = true)
    {

        $this->template = $tpl;
        // 如果没有目录则递归创建
        $this->checkCompilePath();

        $content = $layout ? $this->fetchLayout($tpl) : $this->fetchContent($tpl);

        return $content;

    }

    /**
     * 获取布局文件执行结果
     * @return mixed
     */
    public function fetchLayout($tpl)
    {
        $this->template = $tpl;
        $layoutCompileFile = $this->getLayoutCompileFile();

        if(!$this->checkLayoutCompileFile()){
            //布局文件编译地址
            $layoutCompileFile =$this->getLayoutCompileFile();
            //布局文件内容
            $layoutContent = $this->getlayoutContent();
            //进行占位符替换
            $content = str_replace('{$content}','<?php echo $this->fetchContent(\''.$tpl.'\');?>',$layoutContent);
            // 执行编译
            $compileContent = $this->compile($content);
            // 保存编译文件
            file_put_contents($layoutCompileFile, "<?php if (!defined('TPL_INC')) exit;?>" . $compileContent);
        }

        //变量赋值
        extract($this->vars, EXTR_OVERWRITE);

        // 启用缓冲
        ob_start();
        //加载编译文件
        require $layoutCompileFile;
        // 获取内容
        $content = ob_get_contents();
        ob_end_clean();

        return $content;


    }

    /**
     * 获取模板文件执行结果
     * @return mixed
     * @throws Exception
     */
    public function fetchContent($tpl)
    {
        $this->template = $tpl;
        //编译文件
        $compileFile = $this->getCompileFile();
        //检查编译文件是否有效，无效则做处理
        if (!$this->checkCompileFile()) {
            //模板文件内容
            $templateContent = $this->getTemplateContent();
            // 执行编译
            $compileContent = $this->compile($templateContent);
            // 保存编译文件
            file_put_contents($compileFile, "<?php if (!defined('TPL_INC')) exit;?>" . $compileContent);
        }

        //变量赋值
        extract($this->vars, EXTR_OVERWRITE);

        // 启用缓冲
        ob_start();
        //加载编译文件
        require $compileFile;
        // 获取内容
        $content = ob_get_contents();
        ob_end_clean();

        return $content;
    }

    /**
     * 自定义添加标签
     * @param array $tags
     * @param bool $reg
     * @return mixed
     */
    public function addTags($tags = array(), $reg = false)
    {
        $flag = $reg ? 'reg' : 'str';
        foreach ($tags as $k => $v) {
            $this->_replace[$flag]['search'][] = $k;
            $this->_replace[$flag]['replace'][] = $v;
        }
    }

    /**
     * 模板编译核心
     *
     * @param unknown $content
     * @return string|mixed
     */
    protected function compile($content)
    {
        // 内容为空直接返回
        if (empty($content))
            return '';

        // 进行标签替换
        $cp_template = $this->tag_replace($content);

        // 追加标签解析
        $cp_template = str_replace($this->_replace['str']['search'], $this->_replace['str']['replace'], $cp_template);
        $cp_template = preg_replace($this->_replace['reg']['search'], $this->_replace['reg']['replace'], $cp_template);

        return $cp_template;
    }

    /**
     * 模板语法解析
     *
     * @param unknown $template
     * @return mixed
     */
    protected function tag_replace($template)
    {
        $config = $this->config;
        $left = $config['TPL_TEMPLATE_LEFT'];
        $right = $config['TPL_TEMPLATE_RIGHT'];
        // php标签
        /*
         * {php echo phpinfo();} => <?php echo phpinfo(); ?>
         */
        $template = preg_replace("/" . $left . "php\s+(.+)" . $right . "/", "<?php \\1?>", $template);

        // if 标签
        /*
         * {if $name==1} => <?php if ($name==1){ ?>
         * {elseif $name==2} => <?php } elseif ($name==2){ ?>
         * {else} => <?php } else { ?>
         * {/if} => <?php } ?>
         */
        $template = preg_replace("/" . $left . "if\s+(.+?)" . $right . "/", "<?php if(\\1) { ?>", $template);
        $template = preg_replace("/" . $left . "else" . $right . "/", "<?php } else { ?>", $template);
        $template = preg_replace("/" . $left . "elseif\s+(.+?)" . $right . "/", "<?php } elseif (\\1) { ?>", $template);
        $template = preg_replace("/" . $left . "\/if" . $right . "/", "<?php } ?>", $template);

        // for 标签
        /*
         * {for $i=0;$i<10;$i++} => <?php for($i=0;$i<10;$i++) { ?>
         * {/for} => <?php } ?>
         */
        $template = preg_replace("/" . $left . "for\s+(.+?)" . $right . "/", "<?php for(\\1) { ?>", $template);
        $template = preg_replace("/" . $left . "\/for" . $right . "/", "<?php } ?>", $template);

        // loop 标签
        /*
         * {loop $arr $vo} => <?php $n=1; if (is_array($arr) foreach($arr as $vo){ ?>
         * {loop $arr $key $vo} => <?php $n=1; if (is_array($array) foreach($arr as $key => $vo){ ?>
         * {/loop} => <?php $n++;}unset($n) ?>
         */
        $template = preg_replace("/" . $left . "loop\s+(\S+)\s+(\S+)" . $right . "/", "<?php \$n=1;if(is_array(\\1)) foreach(\\1 AS \\2) { ?>", $template);
        $template = preg_replace("/" . $left . "loop\s+(\S+)\s+(\S+)\s+(\S+)" . $right . "/", "<?php \$n=1; if(is_array(\\1)) foreach(\\1 AS \\2 => \\3) { ?>", $template);
        $template = preg_replace("/" . $left . "\/loop" . $right . "/", "<?php \$n++;}unset(\$n); ?>", $template);

        // 函数 标签
        /*
         * {date('Y-m-d H:i:s')} => <?php echo date('Y-m-d H:i:s');?>
         * {$date('Y-m-d H:i:s')} => <?php echo $date('Y-m-d H:i:s');?>
         */
        $template = preg_replace("/" . $left . "(\S*\(([^{}]*)\))" . $right . "/", "<?php echo \\1;?>", $template);
        $template = preg_replace("/" . $left . "(\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff:]*\(([^{}]*)\))" . $right . "/", "<?php echo \\1;?>", $template);

        // 替换下划线常量/替换全大写字母常量/替换变量/递归加载模板
        /*
         * __APP__ => <?php if(defined('__APP__')){echo __APP__;}else{echo '__APP__';} ?>
         * {CONSTANCE} => <?php echo CONSTANCE;?> 或 {CON_STANCE} => <?php echo CON_STANCE;?>
         * {$var} => <?php echo $var; ?>
         * {include head} => <?php \$this->fetchContent(\"$1\"); ?>
         */
        $template = preg_replace("/__[A-Z]+__/", "<?php if(defined('$0')){echo $0;}else{echo '$0';} ?>", $template);
        $template = preg_replace("/" . $left . "([A-Z_\x7f-\xff][A-Z0-9_\x7f-\xff]*)" . $right . "/s", "<?php if(defined('\\1')){echo \\1;}else{echo '\\1';}?>", $template);
        $template = preg_replace("/" . $left . "(\\$[a-zA-Z_]\w*(?:\[[\w\.\"\'\[\]\$]+\])*)" . $right . "/i", "<?php echo $1; ?>", $template);
        $template = preg_replace("/" . $left . "include\s*(.*)" . $right . "/i", "<?php echo \$this->fetchContent(\"$1\"); ?>", $template);

        // 注释 编译时清除
        /*
         * {*注释内容*}=>
         * {#注释内容#}=>
         */
        $template = preg_replace("/" . $left . "(\*[\s\S]*\*)" . $right . "/", "", $template);
        $template = preg_replace("/" . $left . "(#[\s\S]*#)" . $right . "/", "", $template);

        return $template;
    }




    //======================================================================================//

    /**
     * 设置模板引擎参数
     *
     * @param unknown $key
     * @param string $value
     */
    public function setConfig($key, $value = null)
    {
        if (is_array($key)) {
            $this->config = array_merge($this->config, $key);
        } else {
            $this->config[$key] = $value;
        }
    }


    /**
     * 获取模板文件路径
     * @return string
     * @throws Exception
     */
    public function getTemplateFile()
    {
        $config = $this->config;
        //模板文件路径
        $tplFile = $this->getTemplatePath() . $this->template . $config['TPL_TEMPLATE_SUFFIX'];
        if (!file_exists($tplFile)) {
            throw new Exception($tplFile . "视图文件不存在");
        }
        return $tplFile;
    }

    /**
     * 获取模板目录
     * @return Ambigous <string, \CorePHP\core\View\unknown>
     */
    public function getTemplatePath()
    {
        return $this->config['TPL_TEMPLATE_PATH'];
    }

    /**
     * 获取模板文件内容
     *
     * @param string $tplFile
     * @return string
     */
    public function getTemplateContent()
    {
        $tplFile = $this->getTemplateFile();
        return file_get_contents($tplFile);
    }

    /**
     * 获取编译文件路径
     *
     * @param string $tplFile
     * @return string
     */
    public function getCompileFile()
    {
        $config = $this->config;
        $tplFile = $this->getCompilePath() . $this->getTemplateFile(); // 加入路径,防止编译和缓存路径是同一目录是相互覆盖的问题
        return $this->getCompilePath() . md5($tplFile) . $config['TPL_COMPILE_SUFFIX'];
    }

    /**
     * 获取模板目录
     * @return Ambigous <string, \CorePHP\core\View\unknown>
     */
    public function getCompilePath()
    {
        return $this->config['TPL_COMPILE_PATH'];
    }

    /**
     * 获取编译时间
     *
     * @param string $tplFile
     * @return int|number
     */
    public function getCompileTime()
    {
        $cplFile = $this->getCompileFile(); // 根据模板获取编译地址
        if (file_exists($cplFile)) {
            return filemtime($cplFile); // 返回编译时间
        } else {
            return 0; // 如果没有文件返回0
        }
    }

    /**
     * 检测缓存文件夹是否存在，不存在则创建
     * @throws Exception
     */
    public function checkCompilePath()
    {
        $config = $this->config;
        // 如果没有目录则递归创建
        if (!is_dir($config['TPL_COMPILE_PATH'])) {
            try {
                mkdir($config['TPL_COMPILE_PATH'], 0777, true);
            } catch (ErrorException $e) {
                throw new Exception($config['TPL_COMPILE_PATH'] . '创建失败');
            }
        }
    }

    /**
     * 检查编译文件是否有效
     * @return bool
     * @throws Exception
     */
    public function checkCompileFile()
    {
        $tplTime = filemtime($this->getTemplateFile()); // 模板修改时间
        $compileTime = $this->getCompileTime(); // 编译时间

        $compileFile = $this->getCompileFile(); // 取得编译文件路径
        //检查编译文件是否存在 而且 编译文件修改时间 > 模板文件修改时间 和 布局文件的修改时间 ，满足条件则位true否则false
        return file_exists($compileFile) && $compileTime > $tplTime;

    }
    /**
     * 检查布局编译文件是否有效
     * @return bool
     * @throws Exception
     */
    public function checkLayoutCompileFile()
    {
        $layoutCompileFile = $this->getLayoutCompileFile();
        $layoutTime = filemtime($this->getLayoutFile()); // 模板修改时间

        //检查编译文件是否存在 而且 编译文件修改时间 > 模板文件修改时间 和 布局文件的修改时间 ，满足条件则位true否则false
        return file_exists($layoutCompileFile) && filemtime($layoutCompileFile) > $layoutTime;

    }

    /**
     * 获取布局文件编译路径
     * @return string
     */
    public function getLayoutCompileFile()
    {
        $config = $this->config;
        $tplFile = $this->getCompilePath() . $this->getLayoutFile(); // 加入路径,防止编译和缓存路径是同一目录是相互覆盖的问题
        return $this->getCompilePath() . md5($tplFile) . $config['TPL_COMPILE_SUFFIX'];
    }

    /**
     * 获取布局文件路径
     * @return string
     * @throws Exception
     */
    public function getLayoutFile()
    {
        $layoutFile = $this->getTemplatePath() . $this->config['TPL_LAYOUT_FILE'] . $this->config['TPL_TEMPLATE_SUFFIX'];
        if(!file_exists($layoutFile)){
            throw new Exception('布局文件不存在：'.$layoutFile);
        }
        return $layoutFile;
    }

    /**
     * 布局文件内容
     * @return mixed
     */
    public function getlayoutContent()
    {
        return file_get_contents($this->getLayoutFile());
    }
}